#!/usr/bin/env python
# encoding: utf-8
# author: 04


import datetime
from decimal import Decimal

from django.db import close_old_connections, transaction
from django.db.models import Count, F, Max, Q
from main import _script_head  # noqa
from main import config, models
from main.apps.purification.endless_task.out_order_task_util import (
    add_to_check_similar_audit_order, add_to_sync_audit_order)
from main.apps.purification.models import (AuditOrder, CustomerPurifyConfig,
                                           UserSubOrderAnswer)
from main.apps.purify_autoconf.utils import can_add_similar
from main.common.audit_queue import PurifyMainQueue
from main.common.constants import PURIFY_ANSWER_FREEZE_VOTE
from main.models.data_map import (AUDIT_ORDER_SPLIT, AUDIT_ORDER_WAITING,
                                  AUDIT_RESULT_ILLEGAL, AUDIT_RESULT_NOMAL,
                                  AUDIT_SUBORDER_RESULT_CORRECT,
                                  AUDIT_SUBORDER_RESULT_ERROR, AUDIT_TODO,
                                  QC_TODO, RECHECK_ILLEGAL, RECHECK_NORMAL,
                                  SCORE_TYPE_PURIFY)
from main.schedule.async_tasks.notify import create_single_notify
from main.schedule.async_tasks.purify import (stat_audit_order,
                                              update_rate_for_single_user)
from main.schedule.async_tasks.score import async_add_score
from main.schedule.async_tasks.update_to_audit_num import decrease_to_audit_num
from main.service.base import BaseService
from main.service.purify_suborder import PurifyOrderSerializer
from main.settings import MSG_SCORE


class RecheckError(Exception):
    """ 投票需要复核，可能是产生分歧，或是触发复核规则
    """
    pass


ZSXQ_CODE = {
    "3|^|3": "政治",
    "2|^|3": "色情",
    "22|^|3": "违法",
    "23|^|3": "暴恐",
    "4|^|3": "低俗",
    "1|^|3": "其他违规",
}
ZSXQ_YUE_CODE = {
    # 涉政, 涉⻩, 涉暴, 涉毒, 涉赌, 恶意推⼴, 低俗辱骂
    "涉政": "政治",
    "涉⻩": "色情",
    "涉暴": "暴恐",
    "涉毒": "违法",
    "涉赌": "违法",
    "恶意推⼴": "其他违规",
    "低俗辱骂": "低俗",
}


class PurifyVote(BaseService):
    """ 预期查询频率不会很高，且每次查询得到的数据量不会太大(不超过10k)
    故未进行相应缓存处理，避免过早优化
    如遇到性能瓶颈，建议将 purify_config_score 及相应用户数据缓存，减少级联查询
    """

    loop_step = 2
    lock_multi_duration = 10
    conflict_key = 'purify_vote_process1'
    vote_delay_seconds = PURIFY_ANSWER_FREEZE_VOTE

    def __init__(self):
        super().__init__()
        self.main_queue = PurifyMainQueue()

    def task(self):
        if config.ENV not in ['test_ci', 'dev']:
            close_old_connections()  # https://www.jianshu.com/p/d2ba42feee15  避免长时间运行之后出现 MySQL server has gone away

        vote_delay = datetime.datetime.now() - datetime.timedelta(seconds=self.vote_delay_seconds)
        not_do_query = Q(usersuborderanswer__first_result=AUDIT_TODO)
        customer_qs = CustomerPurifyConfig.objects.annotate(
            purifyconfig_score=F('purifyconfig__score'),
            omission_score=F('purifyconfig__omission_score'),
            error_score=F('purifyconfig__error_score'),
            expire_seconds=F('purifyconfig__order_expire_seconds')
        )
        customer_map = {
            cus.id: cus for cus in customer_qs
        }
        order_qs = AuditOrder.objects.filter(result_sync=AUDIT_ORDER_SPLIT, qc_time__lt=vote_delay).annotate(
            suborder_todo=Count('usersuborderanswer', filter=not_do_query)
        ).filter(
            suborder_todo=0,
        ).annotate(
            last_answer_time=Max('usersuborderanswer__result_time'),
        ).filter(last_answer_time__lte=vote_delay)
        count = order_qs.count()
        if count > 0:
            self.logger.info(f'Purify Vote: Got {count} orders in voting.')
        for order in order_qs[:5000]:
            self.logger.info(f'Purify Vote: {order.id} doing...')
            suborders = order.usersuborderanswer_set.all()
            customer_config = customer_map[order.customer_purifyconfig_id]
            try:
                first_result, second_result, third_result, good_vote_ids, tickets = self.vote_result(
                    suborders, customer_config
                )
            except RecheckError as e:
                self.logger.info(f'Purify Vote: Order {order.id} {e} Lauching Arbitration...')
                suborder_id = self.launch_arbitration(order)
                self.logger.info(f'Purify Vote: Order {order.id} - {suborder_id} put to first.')
                continue
            except ValueError as e:
                self.logger.exception(e)
                continue
            with transaction.atomic():
                order.first_result = first_result
                order.second_result = second_result
                order.third_result = third_result
                order.final_result_time = datetime.datetime.now()
                order.result_sync = AUDIT_ORDER_WAITING
                order.qc_result = QC_TODO
                raw_content = order.raw_content
                decrease_to_audit_num(order)  # 待审核减少
                if can_add_similar(order.customer_purifyconfig.purifyconfig_id, 
                                   order.content_type):
                    add_to_check_similar_audit_order(order.id)
                    # 增加待检查相似订单

                extra = raw_content.get('extra', {}) if raw_content else {}  # tencent order & tencent engine
                AutoDetailCode = extra.get('AutoDetailCode', '') if extra else ''
                AutoDetailCodeLabel = ''
                if not AutoDetailCode:
                    machine_yue = raw_content.get('machine_yue', {}) if raw_content else {}  # zsxq order & yue engine
                    AutoDetailCodeLabel = machine_yue.get('AutoDetailCodeLabel', '') if machine_yue else ''
                if not AutoDetailCodeLabel:
                    order.is_consistent = 0 if (not AutoDetailCode or AutoDetailCode == '-2' or
                                                AutoDetailCode in order.second_result or
                                                (AutoDetailCode == '100' and order.first_result == 'normal')
                                                ) else 1
                else:
                    order.is_consistent = 0 if (not AutoDetailCodeLabel or
                                                ZSXQ_YUE_CODE.get(AutoDetailCodeLabel, 'empty_yue') == ZSXQ_CODE.get(
                                                    order.second_result, 'empty_zsxq') or
                                                (AutoDetailCodeLabel == '正常' and order.first_result == 'normal')
                                                ) else 1
                # 人审 机审是否一致, 1为不一致 0为一致

                total_duration = (datetime.datetime.now() - order.create_time).total_seconds()
                order.timeout_duration = \
                    total_duration - order.customer_purifyconfig.purifyconfig.order_expire_seconds

                order.save()
                for sub in suborders:
                    if not sub.user:  # 线上会出现子订单没有用户的情况，具体原因未知，这里做一下兼容处理， 避免报错阻塞投票进程
                        sub.audit_suborder_result = AUDIT_SUBORDER_RESULT_CORRECT \
                            if sub.id in good_vote_ids else AUDIT_SUBORDER_RESULT_ERROR
                        sub.save()
                        continue
                    if sub.id in good_vote_ids:
                        if sub.remark:
                            order.remark = sub.remark
                            order.save()

                        sub.audit_suborder_result = AUDIT_SUBORDER_RESULT_CORRECT
                        if sub.user.status == 'active-no-score':
                            sub.score = 0
                        else:
                            sub.score = customer_config.purifyconfig_score
                            async_add_score.delay(
                                user_id=sub.user.id,
                                score=sub.score,
                                score_reason='净网成功:%s' % (sub.id),
                                score_type=SCORE_TYPE_PURIFY,
                            )
                    else:
                        sub.audit_suborder_result = AUDIT_SUBORDER_RESULT_ERROR

                        if sub.user.status == 'active-no-score':
                            sub.score = 0
                        else:
                            # 配置中的分值均为正分，此处转为扣费负值
                            score = -(
                                customer_config.omission_score
                                if sub.first_result == AUDIT_RESULT_NOMAL
                                else customer_config.error_score
                            )
                            reason_action = '漏审' if sub.first_result == AUDIT_RESULT_NOMAL else '错审'
                            sub.score = score
                            async_add_score.delay(
                                user_id=sub.user.id,
                                score=score,
                                score_reason=f'审核{reason_action}扣分:%s' % (sub.id),
                                score_type=SCORE_TYPE_PURIFY,
                            )
                            create_single_notify.delay(sub.user.id, {
                                "msg_type": MSG_SCORE,
                                "content": f'{order.content_id}{reason_action}，扣除 {sub.score} 积分。'
                            })
                    sub.trust_rate = tickets[sub.id]
                    sub.save()
            stat_audit_order.delay(order.id)
            add_to_sync_audit_order(order.id)
            self.main_queue.order_unregister(order.id)

    def vote_result(self, suborders, customer_config):
        """ 从多个子订单选定可信的答案
        权重占比： 达人分 占 30% ， 净网错误率的反比占 70% , 总分 100分
        """
        vote_box = {}
        ticket_record = {}
        sep = '|-.-|'  # 特殊连续字符防止被重复
        if suborders.count() == 1:
            done_result = suborders.first().first_result
            normal_recheck = customer_config.recheck_action == RECHECK_NORMAL and done_result == AUDIT_RESULT_NOMAL
            illegal_recheck = customer_config.recheck_action == RECHECK_ILLEGAL and done_result == AUDIT_RESULT_ILLEGAL
            if normal_recheck or illegal_recheck:
                raise RecheckError("One dicision match recheck rule.")
        for suborder in suborders:
            result_key = f'{suborder.first_result}{sep}{suborder.second_result}{sep}{suborder.third_result}'
            purify_rate = 0
            if suborder.user:
                if suborder.first_result == AUDIT_RESULT_NOMAL:
                    purify_rate = suborder.user.purify_miss_rate
                elif suborder.first_result == AUDIT_RESULT_ILLEGAL:
                    purify_rate = suborder.user.purify_error_rate
                else:
                    self.logger.error(
                        f'VoteError: Not Deal Order {suborder.id}.'
                        f'first result is: {suborder.first_result}'
                    )
                    raise ValueError('Order Not Deal.')
            if purify_rate is None:
                update_rate_for_single_user.delay(suborder.user.id)
            if not getattr(suborder.user, 'userscore', None) and suborder.user:  # 没有 userscore， 现场创建一个
                userscore, _ = models.UserScore.objects.get_or_create(user_id=suborder.user.id)
                setattr(suborder.user, 'userscore', userscore)

            genius_score = suborder.user.userscore.genius_score if suborder.user else 0
            vote_weight = rate_to_weight(purify_rate, genius_score)
            vote_box.setdefault(result_key, [0, []])
            vote_box[result_key][0] += vote_weight
            vote_box[result_key][1].append(suborder.id)
            ticket_record[suborder.id] = vote_weight
        result_key, info = sorted(vote_box.items(), key=lambda x: x[1][0], reverse=True)[0]
        if len(info[1]) == 1 and suborders.count() == 2:
            raise RecheckError("The 2 suborder not the same.")
        first_result, second_result, third_result = result_key.split(sep)
        return first_result, second_result, third_result, info[1], ticket_record

    def launch_arbitration(self, order_obj):
        """ 创建仲裁子订单
        """
        order = PurifyOrderSerializer(order_obj).data
        instance = UserSubOrderAnswer.objects.create(
            order_id=order['id'],
        )
        order['suborder_id'] = instance.id
        self.main_queue.put_to_first(order)
        return instance.id


def rate_to_weight(purify_rate, genius_score):
    """ 通过错误率及达人分
    """
    if purify_rate is None:
        purify_score = Decimal('0')
    else:
        purify_score = max((1 - purify_rate * 10), 0) * 100  # 净网权重不能低于0,超过10%错误率的没有净网分
    vote_weight = genius_score * Decimal('0.3') + purify_score * Decimal('0.7')
    return vote_weight


if __name__ == "__main__":
    PurifyVote().run()
